const _ = require('lodash')
const operate = require('./operate')

const helper = require('../utils/helper')
const auto = require('../utils/auto')
const database = require('../utils/database')
const hooksUtil = require('../utils/hooks')
const feature = require('../config/feature')

let hooks = {}
let config = {}

let checkPermission = (operator, model, permission = {}) => {
    let _model = (model || '').split('@')
    let _model_name = _model[0] || ''
    let _conn_name = _model[1] || config.default_db_name

    let _curr_permission = _.get(permission, operator)

    // 不配置表示有所有权限
    if (_curr_permission === undefined) return true

    // =true,开启权限
    if (_curr_permission === true) return true

    // =arr, 精确到表权限
    if (_.isArray(_curr_permission)) {
        // 从后往前匹配
        let _curModel = _.findLast(_curr_permission,f => {
            f = (f || f).split('@')
            let _f_model_name = _.trimStart(f[0] || '', '!');
            let _f_conn_name = f[1] || config.default_db_name

            return _f_model_name === _model_name && _conn_name === _f_conn_name
        });

        // 没找到表示没有权限
        if(!_curModel) return false;

        // !开头明确没有摸个表的权限
        return !_.startsWith(_curModel, '!');
    }

    return false;
}

exports.parseOperator = async (operatorPair) => {
    let featureKeys = Object.keys(feature)

    // 操作符必须独立使用
    if (operatorPair.length > 1) {
        throw new Error(`${featureKeys.join(',')} must be used separately`)
    }

    operatorPair = _.get(operatorPair, '0')

    if (_.isEmpty(operatorPair)) throw new Error('no operator found')

    // 检查操作符必须是支持的特性
    if (!featureKeys.includes(operatorPair[0])) {
        throw new Error(`operator must in ${featureKeys.join(',')}`)
    }

    if (typeof operate[operatorPair[0]] !== 'function') throw new Error(`${operatorPair[0]} is not valid`)

    if(!checkPermission(operatorPair[0],operatorPair[1].$model,config.permission)) throw new Error('no operator permission, please check api-free\'s config')

    let _model = (operatorPair[1].$model || '').split('@')
    let _model_name = _model[0] || ''
    let _conn_name = _model[1] || config.default_db_name

    let default_fun = async (ctx) => {return ctx}
    let _global_before = _.get(hooks, 'before') || default_fun
    let _global_after = _.get(hooks, 'after') || default_fun
    let _conn_before = _.get(hooks, `${_conn_name}.before`) || default_fun
    let _conn_after = _.get(hooks, `${_conn_name}.after`) || default_fun
    let _model_before = _.get(hooks, `${_conn_name}.${_model_name}.index.before`) || default_fun
    let _model_after = _.get(hooks, `${_conn_name}.${_model_name}.index.after`) || default_fun
    let _cur_before = _.get(hooks, `${_conn_name}.${_model_name}.${operatorPair[0]}.before`) || default_fun
    let _cur_after = _.get(hooks, `${_conn_name}.${_model_name}.${operatorPair[0]}.after`) || default_fun

    let promise = Promise.resolve(_global_before({ params: operatorPair[1], data: null }))

    return promise.then(ctx => {
        return _conn_before(ctx)
    }).then(ctx => {
        return _model_before(ctx)
    }).then(ctx => {
        return _cur_before(ctx)
    }).then(async ctx => {
        return operate[operatorPair[0]](ctx)
    }).then(ctx => {
        return _cur_after(ctx)
    }).then(ctx => {
        return _model_after(ctx)
    }).then(ctx => {
        return _conn_after(ctx)
    }).then(ctx => {
        return _global_after(ctx)
    })
}

exports.init = async (c) => {
    config = c
    const rule = {
        db: { type: 'object' },

        auto_gen_model: {
            type: 'bool?', default: false,
        },

        default_db_name: {
            type: 'string?', default: '',
        },
        default_page: {
            type: 'int?', default: 1,
        },
        default_limit: {
            type: 'int?', default: 10,
        },
        permission: {
            type: 'object?', default: {},
        },
    }

    // 参数验证
    helper.validate(rule, config)

    // 生成模型
    config.auto_gen_model && await auto.gen(config)

    // 初始化数据库连接
    await database.init(config)

    // 导入hooks
    hooks = hooksUtil.import(config)
}

exports.getConfig = () => config;
